# -*- coding: utf-8 -*-
#
# Tencent is pleased to support the open source community by making 蓝鲸智云PaaS平台社区版 (BlueKing PaaS Community Edition) available.
# Copyright (C) 2017-2019 THL A29 Limited, a Tencent company. All rights reserved.
# Licensed under the MIT License (the "License"); you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
#     http://opensource.org/licenses/MIT
#
# Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on
# an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the
# specific language governing permissions and limitations under the License.
#
import dpath
import yaml
import yaml.reader
import re
import tempfile

from urllib.parse import urlparse
from ruamel.yaml import YAML
from ruamel.yaml.compat import StringIO
from ruamel.yaml.compat import ordereddict

from backend.bcs_k8s.helm.utils.util import fix_rancher_value_by_type, EmptyVaue
from backend.utils.client import make_dashboard_ctl_client
from backend.bcs_k8s.diff import parser

from backend.bcs_k8s.dashboard.exceptions import DashboardExecutionError


yaml.reader.Reader.NON_PRINTABLE = re.compile(
    '[^\x09\x0A\x0D\x20-\x7E\x85\xA0-\uD7FF\uE000-\uFFFD\U00010000-\U0010FFFF]')


def represent_none(self, _):
    return self.represent_scalar('tag:yaml.org,2002:null', '')


yaml.add_representer(type(None), represent_none)


def ruamel_yaml_load(content):
    # be carefule, ruamel.yaml doesn't work well with dpath
    yaml = YAML()
    return yaml.load(content)


def ruamel_yaml_dump(yaml_obj):
    # be carefule, ruamel.yaml doesn't work well with dpath
    yaml = YAML()
    stream = StringIO()
    yaml.dump(yaml_obj, stream=stream)
    content = stream.getvalue()
    return content


def yaml_load(content):
    return yaml.load(content)


def yaml_dump(obj):
    noalias_dumper = yaml.dumper.SafeDumper
    noalias_dumper.ignore_aliases = lambda self, data: True
    return yaml.dump(obj, default_flow_style=False, Dumper=noalias_dumper)


def sync_dict2yaml(obj_list, yaml_content):
    """
    根据 obj_list 的内容更新 yaml_content
    note: 使用 ruamel.yaml 可以保证 yaml_content 可以在load与dump之后还保持注释内容不丢失
    example
    parameters: obj_list
    [
        {
            "name": "aa",
            "type": "int",
            "value": "1"
        },
        {
            "name": "b.c.e",
            "type": "str",
            "value": "3"
        },
        {
            "name": "dd",
            "type": "int",
            "value": 0
        }
    ]
    parameters: yaml_content
    aa: 2
    xx: 1
    b:
      c:
        e: 4
    result:
    aa: 1
    xx: 1
    dd: 0
    b:
      c:
        e: "3"
    """
    yaml_obj = ruamel_yaml_load(yaml_content)
    for item in obj_list:
        try:
            value = fix_rancher_value_by_type(item["value"], item["type"])
        except EmptyVaue:
            continue
        else:
            update = dict()
            dpath.util.new(update, item["name"], value, separator=".")
            dpath.util.merge(
                dst=yaml_obj,
                src=update,
                separator=".",
                flags=dpath.util.MERGE_REPLACE | dpath.util.MERGE_ADDITIVE)

    content = ruamel_yaml_dump(yaml_obj)
    return content


def sync_yaml2dict(obj_list, yaml_content):
    """
    根据 yaml_content 的内容更新 obj_list
    parameters: obj_list
    [
        {
            "name": "aa",
            "type": "int",
            "value": "1"
        },
        {
            "name": "b.c.e",
            "type": "str",
            "value": "3"
        },
        {
            "name": "dd",
            "type": "int",
            "value": 0
        }
    ]
    parameters: yaml_content
    aa: 2
    xx: 1
    b:
      c:
        e: 4
    result:
    [
        {
            "name": "aa",
            "type": "int",
            "value": 2
        },
        {
            "name": "b.c.e",
            "type": "str",
            "value": "4"
        },
        {
            "name": "dd",
            "type": "int",
            "value": 0
        }
    ]
    """
    yaml_obj = ruamel_yaml_load(yaml_content)
    for idx, item in enumerate(obj_list):
        try:
            value = dpath.util.get(yaml_obj, item["name"], separator=".")
        except KeyError:
            pass
        else:
            try:
                value = fix_rancher_value_by_type(value, item["type"])
            except EmptyVaue:
                continue
            else:
                obj_list[idx]["value"] = value

    return obj_list


def safe_get(data, key, default):
    try:
        return int(dpath.util.get(data, key, separator="."))
    except (KeyError, ValueError):
        return default


def collect_resource_state(kube_client, namespace, content):
    state_keys = ["replicas", "readyReplicas", "availableReplicas", "updatedReplicas"]

    def extract_state_info(data):
        return {key: safe_get(data, "status.%s" % key, 0) for key in state_keys}

    with tempfile.NamedTemporaryFile("w") as f:
        f.write(content)
        f.flush()

        res = kube_client.get_by_file(
            filename=f.name,
            namespace=namespace
        )

    result = {
        "summary": {},
        "items": []
    }
    for item in res["items"]:
        state = extract_state_info(item)
        result["items"].append(state)

    for key in state_keys:
        result["summary"][key] = sum([x[key] for x in result["items"]])

    return result


def merge_valuefile(source, new):
    source = ruamel_yaml_load(source)
    if not source:
        source = ordereddict()

    new = ruamel_yaml_load(new)
    dpath.util.merge(source, new)
    return ruamel_yaml_dump(source)


def dashboard_get_overview(kubeconfig, namespace):
    dashboard_client = make_dashboard_ctl_client(
        kubeconfig=kubeconfig
    )
    dashboard_overview = dashboard_client.overview(
        namespace=namespace,
        parameters=dict()
    )
    return dashboard_overview


def extract_state_info_from_dashboard_overview(overview_status, kind, namespace, name):
    for key in overview_status.keys():
        if key.lower() != "{kind}list".format(kind=kind).lower():
            continue

        for k in overview_status[key].keys():
            if k.lower() == "{kind}s".format(kind=kind).lower():
                obj_list = overview_status[key][k]
                break
        else:
            if "items" in overview_status[key]:
                obj_list = overview_status[key]["items"]

        for item in obj_list:
            if item["objectMeta"]["name"].lower() == name.lower():
                return item

    return dict()


def collect_resource_status(base_url, kubeconfig, app, project_code):
    """
    dashboard_client = make_dashboard_ctl_client(
        kubeconfig=kubeconfig
    )
    """

    def status_sumary(status, app):
        if not status and not app.transitioning_result:
            return {
                "messages": "未找到资源，可能未部署成功，请在Helm Release列表也查看失败原因.",
                "is_normal": False,
                "desired_pods": "-",
                "ready_pods": "-",
            }

        # 暂未实现该类资源状态信息
        if "pods" not in status:
            return {
                "messages": "",
                "is_normal": True,
                "desired_pods": "-",
                "ready_pods": "-",
            }

        messages = [item["message"] for item in status["pods"]["warnings"]]
        messages = filter(lambda x: x, messages)

        desired_pods = safe_get(status, "pods.desired", None)
        ready_pods = safe_get(status, "pods.running", None)
        data = {
            "desired_pods": str(desired_pods),
            "ready_pods": str(ready_pods),
            "messages": "\n".join(messages),
            "is_normal": desired_pods == ready_pods,
        }
        return data

    namespace = app.namespace
    content = app.release.content
    resources = parser.parse(content, app.namespace)
    resources = resources.values()
    release_name = app.name

    dashboard_overview = dashboard_get_overview(
        kubeconfig=kubeconfig,
        namespace=namespace
    )

    result = {}
    structure = app.release.extract_structure(namespace)
    for item in structure:
        kind = item["kind"]
        name = item["name"]

        status = extract_state_info_from_dashboard_overview(
            overview_status=dashboard_overview,
            kind=kind,
            namespace=namespace,
            name=name
        )
        """
        status = {}
        if kind.lower() in ["deployment", "replicaset", "daemonset",
                            "job", "statefulset", "cronjob", "replicationcontroller"]:
            try:
                status = dashboard_client.workload_status(
                    kind=kind,
                    name=name,
                    namespace=namespace,
                    parameters=dict()
                )
            except DashboardExecutionError as e:
                if "handler returned wrong status code: got 404 want 200" in e.output:
                    pass
                else:
                    raise
        """

        if status:
            link = resource_link(
                base_url=base_url,
                kind=kind,
                project_code=project_code,
                name=name,
                namespace=namespace,
                release_name=release_name
            )
        else:
            link = None

        key = "{kind}/{namespace}/{name}".format(
            name=name,
            namespace=namespace,
            kind=kind,
        )
        result[key] = {
            "namespace": namespace,
            "name": name,
            "kind": kind,
            "status": status,
            "status_sumary": status_sumary(status, app),
            "link": link
        }
    return result


def get_base_url(request):
    base_url = request.META.get("HTTP_REFERER") or request.META.get("HTTP_HOST")
    base_url = base_url.split("/console/bcs")[0]
    return base_url


def resource_link(base_url, kind, project_code, name, namespace, release_name):
    kind_map = {
        "deployment": "deployments",
        "statefulset": "statefulset",
        "daemonset": "daemonset",
        "job": "job",
    }
    kind = kind.lower()
    if kind not in kind_map:
        return None

    fix_kind = kind_map[kind]
    url = ("/console/bcs/{project_code}/app/{fix_kind}/{resource_name}/{namespace}/{kind}"
           "?name={resource_name}&namespace={namespace}&category={kind}").format(
               base_url=base_url,
               kind=kind.lower(),
               fix_kind=fix_kind,
               instance_name=release_name,
               resource_name=name,
               project_code=project_code,
               namespace=namespace)
    return url


def compose_url_with_scheme(url, scheme="http"):
    """组装URL
    """
    url_split_info = url.split('//')
    return '{scheme}://{domain}'.format(scheme=scheme, domain=url_split_info[-1])
